/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.dsl.test.jms;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.ChannelInterceptorAware;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.FixedSubscriberChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.MessageSources;
import org.springframework.integration.dsl.MessagingGateways;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.dsl.core.Pollers;
import org.springframework.integration.dsl.jms.Jms;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Artem Bilan
* @author Gary Russell
* @author Nasko Vasilev
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class JmsTests {
@Autowired
private ListableBeanFactory beanFactory;
@Autowired
private ControlBusGateway controlBus;
@Autowired
@Qualifier("flow1QueueChannel")
private PollableChannel outputChannel;
@Autowired
@Qualifier("jmsOutboundFlow.input")
private MessageChannel jmsOutboundInboundChannel;
@Autowired
@Qualifier("jmsOutboundInboundReplyChannel")
private PollableChannel jmsOutboundInboundReplyChannel;
@Autowired
@Qualifier("jmsOutboundGatewayFlow.input")
private MessageChannel jmsOutboundGatewayChannel;
@Autowired
private TestChannelInterceptor testChannelInterceptor;
@Autowired
private ConnectionFactory jmsConnectionFactory;
@Autowired
private PollableChannel jmsPubSubBridgeChannel;
@Autowired
@Qualifier("jmsOutboundGateway.handler")
private MessageHandler jmsOutboundGatewayHandler;
@Autowired
private AtomicBoolean jmsMessageDrivenChannelCalled;
@Autowired
private AtomicBoolean jmsInboundGatewayChannelCalled;
@Test
public void testPollingFlow() {
this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.start()");
assertThat(this.beanFactory.getBean("integerChannel"), instanceOf(FixedSubscriberChannel.class));
for (int i = 0; i < 5; i++) {
Message<?> message = this.outputChannel.receive(20000);
assertNotNull(message);
assertEquals("" + i, message.getPayload());
}
this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.stop()");
assertTrue(((ChannelInterceptorAware) this.outputChannel).getChannelInterceptors()
.contains(this.testChannelInterceptor));
assertThat(this.testChannelInterceptor.invoked.get(), Matchers.greaterThanOrEqualTo(5));
}
@Test
public void testJmsOutboundInboundFlow() {
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload("hello THROUGH the JMS")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "jmsInbound")
.build());
Message<?> receive = this.jmsOutboundInboundReplyChannel.receive(10000);
assertNotNull(receive);
assertEquals("HELLO THROUGH THE JMS", receive.getPayload());
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload("hello THROUGH the JMS")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "jmsMessageDriver")
.build());
receive = this.jmsOutboundInboundReplyChannel.receive(10000);
assertNotNull(receive);
assertEquals("hello through the jms", receive.getPayload());
assertTrue(this.jmsMessageDrivenChannelCalled.get());
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload(" foo ")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "containerSpecDestination")
.build());
receive = this.jmsOutboundInboundReplyChannel.receive(10000);
assertNotNull(receive);
assertEquals("foo", receive.getPayload());
}
@Test
public void testJmsPipelineFlow() {
assertEquals(new Long(10000),
TestUtils.getPropertyValue(this.jmsOutboundGatewayHandler, "idleReplyContainerTimeout", Long.class));
PollableChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload("hello through the jms pipeline")
.setReplyChannel(replyChannel)
.setHeader("destination", "jmsPipelineTest")
.build();
this.jmsOutboundGatewayChannel.send(message);
Message<?> receive = replyChannel.receive(5000);
assertNotNull(receive);
assertEquals("HELLO THROUGH THE JMS PIPELINE", receive.getPayload());
assertTrue(this.jmsInboundGatewayChannelCalled.get());
}
@Test
public void testPubSubFlow() {
JmsTemplate template = new JmsTemplate(this.jmsConnectionFactory);
template.setPubSubDomain(true);
template.setDefaultDestinationName("pubsub");
template.convertAndSend("foo");
Message<?> received = this.jmsPubSubBridgeChannel.receive(5000);
assertNotNull(received);
assertEquals("foo", received.getPayload());
}
@Autowired
private CountDownLatch redeliveryLatch;
@Test
public void testJmsRedeliveryFlow() throws InterruptedException {
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload("foo")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "jmsMessageDriverRedelivery")
.build());
assertTrue(this.redeliveryLatch.await(10, TimeUnit.SECONDS));
}
@MessagingGateway(defaultRequestChannel = "controlBus.input")
private interface ControlBusGateway {
void send(String command);
}
@Configuration
@ImportAutoConfiguration({ ActiveMQAutoConfiguration.class, JmxAutoConfiguration.class, IntegrationAutoConfiguration.class })
@IntegrationComponentScan
@ComponentScan
public static class ContextConfiguration {
@Autowired
private ConnectionFactory jmsConnectionFactory;
@PostConstruct
public void init() {
((ActiveMQConnectionFactory) this.jmsConnectionFactory).setTrustAllPackages(true);
}
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedRate(500).get();
}
@Bean
public IntegrationFlow controlBus() {
return IntegrationFlowDefinition::controlBus;
}
@Bean
@InboundChannelAdapter(value = "flow1.input", autoStartup = "false", poller = @Poller(fixedRate = "100"))
public MessageSource<?> integerMessageSource() {
MethodInvokingMessageSource source = new MethodInvokingMessageSource();
source.setObject(new AtomicInteger());
source.setMethodName("getAndIncrement");
return source;
}
@Bean
public IntegrationFlow flow1() {
return f -> f
.fixedSubscriberChannel("integerChannel")
.transform("payload.toString()")
.channel(Jms.pollableChannel("flow1QueueChannel", this.jmsConnectionFactory)
.destination("flow1QueueChannel"));
}
@Bean
public IntegrationFlow jmsOutboundFlow() {
return f -> f.handleWithAdapter(h -> h.jms(this.jmsConnectionFactory)
.destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER));
}
@Bean
public MessageChannel jmsOutboundInboundReplyChannel() {
return MessageChannels.queue().get();
}
@Bean
public IntegrationFlow jmsInboundFlow() {
return IntegrationFlows
.from((MessageSources s) -> s.jms(this.jmsConnectionFactory).destination("jmsInbound"))
.<String, String>transform(String::toUpperCase)
.channel(this.jmsOutboundInboundReplyChannel())
.get();
}
@Bean
public IntegrationFlow pubSubFlow() {
return IntegrationFlows
.from(Jms.publishSubscribeChannel(this.jmsConnectionFactory)
.destination("pubsub"))
.channel(c -> c.queue("jmsPubSubBridgeChannel"))
.get();
}
@Bean
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(this.jmsConnectionFactory)
.outputChannel(jmsMessageDriverInputChannel())
.destination("jmsMessageDriver"))
.<String, String>transform(String::toLowerCase)
.channel(jmsOutboundInboundReplyChannel())
.get();
}
@Bean
public AtomicBoolean jmsMessageDrivenChannelCalled() {
return new AtomicBoolean();
}
@Bean
public MessageChannel jmsMessageDriverInputChannel() {
DirectChannel directChannel = new DirectChannel();
directChannel.addInterceptor(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
jmsMessageDrivenChannelCalled().set(true);
return super.preSend(message, channel);
}
});
return directChannel;
}
@Bean
public IntegrationFlow jmsMessageDrivenFlowWithContainer() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(
Jms.container(this.jmsConnectionFactory, "containerSpecDestination")
.pubSubDomain(false)
.taskExecutor(Executors.newCachedThreadPool())
.get()))
.transform(String::trim)
.channel(jmsOutboundInboundReplyChannel())
.get();
}
@Bean
public IntegrationFlow jmsOutboundGatewayFlow() {
return f -> f.handleWithAdapter(a ->
a.jmsGateway(this.jmsConnectionFactory)
.replyContainer(c -> c.idleReplyContainerTimeout(10))
.requestDestination("jmsPipelineTest"),
e -> e.id("jmsOutboundGateway"));
}
@Bean
public IntegrationFlow jmsInboundGatewayFlow() {
return IntegrationFlows.from((MessagingGateways g) ->
g.jms(this.jmsConnectionFactory)
.requestChannel(jmsInboundGatewayInputChannel())
.destination("jmsPipelineTest"))
.<String, String>transform(String::toUpperCase)
.get();
}
@Bean
public AtomicBoolean jmsInboundGatewayChannelCalled() {
return new AtomicBoolean();
}
@Bean
public MessageChannel jmsInboundGatewayInputChannel() {
DirectChannel directChannel = new DirectChannel();
directChannel.addInterceptor(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
jmsInboundGatewayChannelCalled().set(true);
return super.preSend(message, channel);
}
});
return directChannel;
}
@Bean
public IntegrationFlow jmsMessageDrivenRedeliveryFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(this.jmsConnectionFactory)
.errorChannel(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)
.destination("jmsMessageDriverRedelivery"))
.<String, String>transform(p -> {
throw new RuntimeException("intentional");
})
.get();
}
@Bean
public CountDownLatch redeliveryLatch() {
return new CountDownLatch(3);
}
@Bean
public IntegrationFlow errorHandlingFlow() {
return IntegrationFlows.from(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)
.handle(m -> {
MessagingException exception = (MessagingException) m.getPayload();
redeliveryLatch().countDown();
throw exception;
})
.get();
}
}
@Component
@GlobalChannelInterceptor(patterns = "flow1QueueChannel")
public static class TestChannelInterceptor extends ChannelInterceptorAdapter {
private final AtomicInteger invoked = new AtomicInteger();
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
this.invoked.incrementAndGet();
return message;
}
}
}